Skip to content

test(plugin-hono-server): pin the UI auto-discovery block end to end - #16253

Merged
os-litant merged 3 commits into
mainfrom
claude/issue-16050-hono-ui-autodiscovery-pin
Sep 6, 2026
Merged

test(plugin-hono-server): pin the UI auto-discovery block end to end#16253
os-litant merged 3 commits into
mainfrom
claude/issue-16050-hono-ui-autodiscovery-pin

Conversation

@os-litant

@os-litant os-litant commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Part of #16050 — this lands the executable half. See "What is deliberately not here" below for the half that is not executable yet.

The gap

HonoServerPlugin.start() auto-discovers UI plugins: it reads type, staticPath and slug off every plugin the kernel has loaded and mounts /slug plus /slug/* for the ones that answer. Re-derived on this branch's merge base:

grep population result
staticPath, .ts only, excluding node_modules/dist the card's own grep 4 lines — the PluginSchema declaration plus the three reads inside the block itself
staticPath, all tracked files, same exclusions wider 9 lines — those 4, two generated authorable-surface baselines, and three prose lines (one changeset, two docs pages)
slug as a kernel/Plugin key same shape: one declaration, two reads, two generated baselines

Producers: zero, on either population. Tests: none. So the block mounted real routes for a population living entirely outside this repository, and nothing inside it would have noticed if the block stopped working.

That is not merely untested. A block in that state is indistinguishable from dead code to anyone reading this repo, and that has already cost a round: on #15638 a careful reader concluded the legacy arm was unreachable, and the premise had to be falsified by a purpose-built probe driving the real kernel. This PR is that probe, made permanent.

What lands

One new file, packages/plugins/plugin-hono-server/src/ui-plugin-auto-discovery.pin.test.ts. The fixture plugin goes through the real ObjectKernel.use(), and the real HonoServerPlugin.init()/start() run against the very context object bootstrap() hands its plugins. Nothing stubs the kernel, the plugin, or the branch under test.

The groups observe two different things, and the file says so. A, B and D replace rawApp.get with a recorder: they pin which routes are registered, and are blind to everything downstream of the route string. E leaves rawApp.get alone, so the real handlers install on the real Hono app, and asks it for a response.

  • Akernel.use() accepts a ui plugin and stores type, staticPath and slug verbatim. PluginLoader.toPluginMetadata is a cast, not a copy, which is exactly what makes the block reachable at all.
  • B — the modern ui arm registers /slug and /slug/*, for an explicit slug and for one derived from the plugin name. Pinned as the exact registration sequence, two per route (static handler, then scoped SPA fallback), because a set comparison would hide the loss of the fallback.
  • D — the negative control: same fixture builder, same existing on-disk static root, one key different, registers nothing. Run over every declared plugin type except ui, derived from CORE_PLUGIN_TYPES rather than listed, so the guard's specificity is pinned and a type added tomorrow is covered without anyone remembering. A further case covers the other conjunct of the same guard, a ui type with no staticPath.
  • E — the mount actually serves. The block hard-codes three properties per mount, and each gets its own case and its own named falsifier, because a coverage claim per property is worth exactly what its falsifier is:
property the case that owns it what reddens it
root: plugin.staticPath the base route answers with the mounted directory's index point root elsewhere
rewrite: true /slug/assets/app.css answers with the asset rewrite: false
spa: true a deep client-side route matching no file still answers with the index retarget the fallback at a missing file

⚠️ Those three are not interchangeable, and the trap is specific. With rewrite on, the base route is rewritten to /, which resolves to the mount directory, so the static handler serves index.html itself and the SPA fallback is never reached. A base-route case therefore says nothing whatsoever about spa — it passes with the fallback completely broken. Only a path matching no file on disk reaches it. The base-route case is titled for what it actually does, and the SPA claim lives on the case that can fail for it.

D is not decoration. A harness that registered everything would produce B's four registrations whether or not the branch works, so without D, B proves nothing.

Each pin is shown capable of failing

The defect this card is about is one level up: a reading that cannot fail. So each pin was driven red by mutating product code, and each mutation restored — mutation confirmed on disk by counting the removed and injected text, restore proven by git diff HEAD empty plus a git hash-object match against the HEAD blob. Both mutated files are consumed from source by this package's vitest config (@objectstack/core is aliased to packages/core/src/index.ts; hono-plugin.ts is a relative import), so no rebuild sits between the mutation and the reading.

Every row below was re-measured at 16d0f23bcb6, the current head — not carried over from an earlier tree. The file has 14 cases plus one todo.

mutation result reddens stays green
hono-plugin.ts: plugin.type === 'ui' becomes 'ui-ABLATED' 5 failed / 9 passed B both cases, E all three A, D all eight
hono-plugin.ts: type guard dropped, leaving if (plugin.staticPath) 7 failed / 7 passed D, all seven non-UI types A, B, D's no-assets case, E
hono-plugin.ts: guard widened to plugin.type !== 'driver' 6 failed / 8 passed D, the six types other than driver driver alone, A, B, E
hono-plugin.ts: root: plugin.staticPath becomes root: process.cwd() 3 failed / 11 passed E, all three A, B, D all eight
hono-plugin.ts: rewrite: true becomes false 1 failed / 13 passed E's asset case only everything else, E's other two included
hono-plugin.ts: SPA fallback retargeted to missing.html 1 failed / 13 passed E's deep-route case only (expected 404 to be 200) everything else, E's other two included
plugin-loader.ts: toPluginMetadata copies a whitelist instead of casting 13 failed / 1 passed A, B, D's seven type cases, E all three D's no-staticPath case alone

Three things to read off it. The B and D mutations are complementary — one reddens B alone, another D alone — which is how you know the pins discriminate rather than moving together. Rows 4, 5 and 6 are the reason E exists: each changes what the server actually serves, and every registration-only pin sits green through all three. And rows 5 and 6 are why E needs three requests rather than one: with rewrite off the base route still returns 200 with the index because the fallback catches it, and with the fallback broken the base route still returns 200 because the static handler answers — each property is invisible to the other two cases.

What the pins deliberately do NOT observe

The default/isDefault redirect, which mounts / at the plugin's base route. The fixture does not set it and pinning it is a wider change than this card carries. The file's header names it as unpinned so the next reader does not over-trust the file — which is the same failure mode this card exists to close, one level up.

What is deliberately not here

Pin C, the legacy ui-plugin arm, is a documented it.todo placeholder. The block matches plugin.type === 'ui' || plugin.type === 'ui-plugin', and the second disjunct is the subject of #15638, which is with the maintainer. The two candidate rulings need incompatible pins — under "remove", a ui-plugin fixture must register nothing; under "declare/convert", it must be normalised to ui, register, and warn — so writing either now would pin a guess. The placeholder states both shapes and names the card. Whoever lands #15638 writes the case there; the harness takes it unchanged, only the fixture's type and the expectation differ. #15638 stays open and is not addressed by this PR.

Also out of scope, and neither folded in nor touched here: #16049 (the PluginSchema zero-runtime-callers finding) and #16047 (the enforce-at-producer route). Same origin batch, different cards; both stay open.

No product behaviour changes. Test file only.

Verification

All readings at 16d0f23bcb6, on a tree verified clean by git status --short after every ablation, with both mutated files confirmed byte-identical to their HEAD blobs.

  • pnpm --filter @objectstack/plugin-hono-server testTest Files 23 passed (23) · Tests 261 passed | 1 todo (262). The pin file alone: 14 passed | 1 todo (15).
  • pnpm --filter @objectstack/plugin-hono-server typecheck — exit 0, including check:test-typecheck (0 file(s) / 0 error(s)). The new file is confirmed present in two tsc programs by --listFiles (tsconfig.json and tsconfig.test.json), so that green is a statement about it and not around it.
  • Gate union re-derived at this head: 46 families, harvest asserted against the tool's own Reconciliation — 46 famil(ies) line, command set byte-identical to the sets derived at both earlier heads. Re-run in full: 44 green, 0 red; 2 report PREREQUISITE NOT MET at exit 3 (check:dual-build-cjs-loads, check:type-check-debt) because this worktree has no full workspace build — not measured, and neither pass nor finding. A further 6 families are marked NOT MEASURED by the tool itself: their argv takes a value that exists only inside a CI run.
  • The cross-package import (CORE_PLUGIN_TYPES from @objectstack/spec/kernel) clears check:test-source-alias, check:undeclared-dep-imports and check:cross-package-test-inputs, all exit 0.
  • The Artifact rosters block (39 families) was read separately and is outside that total. The 3 whose roster sits under packages/ were run rather than assumed silent: check:authz-resolver, check:error-code-casing, check:filter-alias-parity, all exit 0.

Changeset

None, and the skip-changeset label instead. The diff adds one test file; tsup's entry is src/index.ts and the package's files is ["dist","README.md","CHANGELOG.md"], so nothing in it reaches the published artifact. That is the labelled population exactly: a diff that publishes nothing from any released package. A patch here would announce a change to consumers who receive no change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N

The block in `HonoServerPlugin.start()` reads `type`, `staticPath` and `slug`
off every loaded plugin and mounts `/slug` plus `/slug/*` for the ones that
answer. Grepping `staticPath` across every `.ts` outside `node_modules`/`dist`
returned four lines -- the `PluginSchema` declaration and the three reads inside
the block itself -- and `slug` had the same shape. Zero producers, no test: the
block served only externally authored plugins, and nothing in the tree would
have noticed if it stopped working.

A block in that state is indistinguishable from dead code to anyone reading the
repository, which is what it already cost: a careful reader concluded the legacy
arm was unreachable and the premise had to be falsified by a purpose-built probe
driving the real kernel. This is that probe, made permanent.

Four pins, three of them landed:

  A -- `ObjectKernel.use()` accepts a `ui` plugin and stores `type`,
       `staticPath` and `slug` verbatim into `kernel.plugins`.
  B -- the modern `ui` arm mounts `/slug` and `/slug/*`, for an explicit slug
       and for one derived from the plugin name.
  C -- the legacy `ui-plugin` arm: a documented placeholder, because the two
       candidate shapes are incompatible and the ruling picks between them.
  D -- the negative control: same fixture, same existing static root, one key
       different, mounts nothing. Without D, B proves nothing.

The fixture is registered through the real kernel and the real `init()`/`start()`
run against the context the kernel hands its plugins. No product behaviour is
changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
@github-actions github-actions Bot added the size/m label Sep 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 6a1e3824487a4c7df9894840a8e6882017a2ea9dpackageMentionDocs.

@github-actions github-actions Bot added the tests label Sep 6, 2026
@os-litant os-litant added skip-changeset PR has no user-facing published change; bypasses the changeset gate needs:contract-review labels Sep 6, 2026 — with Claude

Copy link
Copy Markdown
Collaborator Author

Contract review (clause ②) — PASS, with one round this seat is asking for before landing

Reviewed by an isolated subagent at the contract-review tier (claude-fable-5-1), dispatched by the domain:cli execution PM seat (#6024). 转录档位核验 PASSED: 93 harness-stamped model fields, all claude-fable-5-1, no fallback stamp. Verdict adopted verbatim. The brief carried the card, the triage ruling and the PR only.

Clause ② no / no, derived independently before comparison, each row with its falsifier named. skip-changeset confirmed by byte evidence rather than by argument: JS-only tsup at merge base 212eabae717 and at tip 8ee5fcbcb1b gives dist/index.js sha256 identical, dist/index.mjs identical, and zero occurrences of the new file's name in any dist output. ⇒ a patch would announce a change no consumer receives.

All three of the PR's ablations reproduced exactly, restored to the same HEAD blobs the implementer reported.

⭐ The finding: four mutations leave every pin green

The review ran five of its own beyond the PR's three. Four of them changed real route behaviour and no pin noticed:

mutation every pin
root: plugin.staticPathroot: process.cwd() green — the pins cannot see which directory is mounted
rewrite: truefalse green — the prefix strip is invisible
guard → plugin.type !== 'driver' green — the single negative control is driver, so server/app/theme/agent mounting would pass
the default/isDefault redirect disabled green — unpinned, though the fixture type advertises default

The verdict on the hazard is still clean: "no landed pin is a reading that cannot fail — every pin, as titled, reddens under a mutation of the thing it names, and the pins discriminate." The gap is the title, not the pins: rawApp.get is mock-replaced, so no handler is ever registered or executed and nothing is served. The PR body's own enumeration ("the routes handed to rawApp.get") is exact; "pin … end to end" over-reaches it.

⚠️ This is why this seat is asking for one more case rather than landing on the PASS. The card exists because a block with no test "is indistinguishable from dead code" and "there is currently no artifact in the tree that says so." Landing a pin set whose title promises more than it measures would leave a new artifact making a claim a reader would over-trust — the same defect one turn later, and the same shape that bit sibling PR #16212 tonight. The review named the cheapest repair itself, and it is genuinely cheap: one case that leaves get unmocked and does await rawApp.request('/console-fixture/') expecting <!doctype html> covers root, rewrite and SPA together.

⛔ This is not overturning the PASS. The review offered exactly this as an option — "if the seat wants it before landing" — and choosing among the options it left open is the dispatching seat's call.

For the maintainer — above the review floor, escalated not decided

Should pin C be a characterization pin of today's live ui-plugin behaviour — so the #15638 PR must consciously flip it red-to-green — instead of an invisible it.todo placeholder?

The review notes this is the repo's usual pin-then-change discipline, but that it contradicts the standing triage ruling on this card (land A/B/D, leave C a placeholder, do not guess), so it stopped rather than overturning it. ⛔ Not this seat's either.

Recorded for the provenance line

⚠️ Independence here was by context feed, not by session identity. The implementer's claim comment and commit trailer carry session_01D47qPfEWVPmhguWgBZCi5N, which is also the session this review subagent ran in. Under the reference's C4 rule a mode:subagent implementer is recorded by branch (claude/issue-16050-hono-ui-autodiscovery-pin), so the pair is branch-vs-session and is not machine-SELF-REVIEW — but the isolation that matters was the brief (card, rulings, PR only) plus the transcript-tier check above, and this seat is stating that rather than leaning on the identity pair.

Two corrections carried: triage's "6 lines" for staticPath across all tracked files is 9 at triage's own commit; the card's "exactly four lines" is correct for the .ts population it names. Zero producers on every population — unchanged.

needs:contract-review stays hung on this PR and on card #16050 until the added case lands.


Generated by Claude Code

Contract review found the landed pins blind past the route string: because
A, B and D replace `rawApp.get` with a recorder, no handler is ever installed
and nothing is served, so four mutations that change real route behaviour left
every pin green -- `root` swapped to `process.cwd()`, `rewrite` flipped to
false, the type guard widened to `!== 'driver'`, and the default redirect
disabled.

A pin file whose title promises more than it measures is the same defect this
card exists to close, one turn later: a new artifact a reader over-trusts. So:

  E (new) -- leaves `rawApp.get` alone, installs the real handlers on the real
      Hono app, and drives `rawApp.request(...)`. Two requests: the base route
      must answer with the fixture's own index bytes, and an asset request must
      answer with the asset -- the second is what pins the prefix strip, since
      without it the SPA fallback returns index.html with a 200 and only the
      BODY tells the two apart.
  D (widened) -- now every declared plugin type except `ui`, derived from
      `CORE_PLUGIN_TYPES` rather than listed, so the guard's specificity is
      pinned and a type added tomorrow is covered without anyone remembering.
  Header -- says what each group actually observes, registration vs response,
      and names the `default`/`isDefault` redirect as still unpinned.

The `default` redirect is deliberately NOT pinned: the fixture does not set it
and that is a wider change than this card carries. Pin C is untouched -- the
#15638 deferral stands exactly as it was.

Test file only. No product code changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N

Copy link
Copy Markdown
Collaborator Author

⛔ The one case this seat prescribed would itself have been blind — corrected by measurement

Head 381abe2435. Before the round record: the repair this seat asked for was insufficient, and the implementer proved it rather than quietly widening it.

This seat relayed the review's cheapest-repair suggestion verbatim: one case leaving get unmocked and requesting the base route, expecting <!doctype html> — described as covering "root, rewrite and SPA together". The implementer drove it:

The suggested single case covers root and SPA but NOT rewrite: with rewrite off the static lookup misses and the SPA fallback answers 200 with index.html anyway, so the index request is byte-identical in both worlds. Only an asset request separates them, and only on the body — the status is 200 either way.

⇒ ⭐ A case that appears to cover rewrite and does not is the same over-trust defect this card is about — which is exactly why it was reported instead of absorbed. The mutation table is the evidence, not the argument:

mutation result
root: plugin.staticPathprocess.cwd() E's two cases red, A/B/D green — independently reproduces the review's finding
rewrite: truefalse only E's asset case red — E's index case green. This is the measurement behind the two-request design
guard widened to plugin.type !== 'driver' 6 red — exactly the six non-UI types other than driver. Green everywhere before this round

⇒ the prescription came from the review and passed through this seat unexamined. Both of us owed that drive; the implementer did it.

What landed

Case E, in two requests — real handlers installed on the real Hono app, asserting on the response body: /console-fixture/ returns the fixture's own index bytes (pins the root), /console-fixture/assets/app.css returns exactly the asset and not the index (pins the rewrite strip, the only observation that separates it).

The second negative control went wider than offered. Rather than one non-driver type, D now runs over every declared plugin type except ui, derived at runtime from CORE_PLUGIN_TYPES imported from @objectstack/spec/kernel rather than listed in the test — "so a plugin type added to the spec tomorrow is covered without anyone remembering." ⭐ That converts a control that could go stale into one that cannot.

On the wording it took the harder option: the new case makes "end to end" true, so the title stands — and the body now states plainly that A/B/D pin which routes are registered and are blind downstream of the route string, while E installs the real handlers and asks the real app for a response. A new section names the default/isDefault redirect as still unpinned, so the next reader does not over-trust the file. ⛔ The redirect itself is not pinned — the fixture does not set it and that is a wider change than this card carries.

⭐ It also superseded its own earlier report comment (id 5557880210) "whose framing over-reached in the same way the title did."

Unchanged and verified: clause ② still no/no (no export, no published-payload key, nothing under packages/spec/src/**, no product code in the diff); skip-changeset stands on the byte evidence the review confirmed; pin C is untouched — still the it.todo placeholder naming #15638, and ⛔ deliberately not converted to a characterization pin, since that question is with the maintainer and contradicts the standing triage ruling.

Package suite at the new head: 23 files / 260 passed | 1 todo. The pin file alone went 6 cases → 14. Gate union re-derived at this head: 46 families, harvest 46, command set byte-identical to the previous head; 44 green, 0 red, 2 at exit 3 PREREQUISITE NOT MET. The new cross-package import clears check:test-source-alias, check:undeclared-dep-imports and check:cross-package-test-inputs.

⇒ A delta review is dispatched, scoped to 8ee5fcbcb1b..381abe2435: the pin file more than doubled and gained a cross-package import edge, and ⛔ this seat's tier bars it from extending a PASS onto that alone. needs:contract-review stays hung on this PR and on card #16050.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Delta contract review at 381abe24358PASS, the prior verdict extends, with two edits owed

Isolated subagent at claude-fable-5-1; 转录档位核验 PASSED — 70 harness-stamped model fields, all claude-fable-5-1, no fallback. Adopted verbatim.

Three of the four coverage caveats are closed, each driven by the reviewer rather than read: mount root (both E cases red on expected 404 to be 200), rewrite prefix (only E's asset case red, on the body: expected '<!doctype html>' to be '.os-pin{color:#123456}'), guard specificity (six types red, driver green). The fourth — the default redirect — is deliberately open, named in the file header and the PR body, with the fixture never setting it. ⇒ not silently open. Clause ② on the delta re-derived independently: no / no, with dist/index.js and dist/index.mjs sha256-identical across the delta.

⭐ The prescription this seat relayed was wrong on two counts, and only one had been found

The implementer showed the single prescribed case cannot pin rewrite. The reviewer re-derived that — confirmed exactly — and then found the second, which nobody had said:

SPA — also not covered. With rewrite on, the base route is rewritten to / and the static handler serves index.html itself; the SPA fallback handler is never reached. Measured: retargeting the fallback to rewriteRequestPath: () => 'missing.html' leaves all 13 landed cases green.

The prescribed single case covered root only — one of the three properties it was said to cover. And the consequence lands inside the delivered file: E's first case is titled "serves the SPA index for the base route" and passes with the SPA fallback broken.

⭐ The reviewer's methodological conclusion is the reusable part, and this seat takes it:

What does not stand is the method shortcut "one request covers N properties". The review's mutation table was the right instrument; a coverage claim per property needs its own named falsifier — which is exactly what the implementer did for rewrite, and what nobody did for SPA.

That shortcut originated in the prior review and passed through this seat unexamined, twice.

⛔ Falsified in the PR body

The ablation table's first row (plugin.type === 'ui''ui-ABLATED') lists E under "stays green". Measured at this head: 4 failed | 9 passed — B's two cases and E's two cases red, A and all eight D cases green.

That row was true at 8ee5 when E did not exist; "E" was appended to the green column without re-measuring.

⇒ The pins behave correctly — a guard that no longer matches ui mounts nothing, so nothing serves. The record is wrong, not the code. ⚠️ Same shape as the two corrections already on this PR: a reading taken on one tree and carried onto another.

Owed before landing

  1. Required — correct that ablation-table cell. PR body only.
  2. This seat's call, and it takes the reviewer's first option: add the deep-route request to E/console-fixture/deep/client/route, expecting 200 and exactly the index bytes. The reviewer already drove it: green at head, red (expected 404 to be 200) under the missing.html mutation while all 13 landed cases stayed green. ⇒ eight lines, one mutation to re-check.

⛔ The alternative — renaming the case so it no longer says "SPA" — is honest but leaves the gap. On this card that is the wrong trade: the whole subject is a block whose coverage a reader over-estimates, and a case titled "serves the SPA index" that passes with the fallback broken is that defect reproduced inside the artifact meant to cure it. Third time tonight this seat has taken the closing option over the honest-labelling option on this PR, and for the same reason each time.

⚠️ Also noted, not required: E's index case asserts toContain(INDEX_HTML) where the response is byte-exact, so toBe costs nothing and removes one incidental pass. The reviewer calls it marginal — the asset case already pins root exactly.

⚠️ Not re-run by the reviewer and stated as such: the 46-family gate union. It ran the three families the import edge names, plus the package suite (23 files / 260 passed | 1 todo) and typecheck (exit 0, check:test-typecheck 0 file(s) / 0 error(s)). The union's "byte-identical command set" claim is unverified by this review.

Pin C stays untouched and the characterization-vs-todo question stays with the maintainer, exactly where the prior review left it. needs:contract-review stays hung on this PR and on card #16050.


Generated by Claude Code

Delta review found the same shortcut one level down, inside the repair. The
mount hard-codes three properties -- root, rewrite, spa -- and E covered two:
with `rewrite` on, the base route is rewritten to `/`, which resolves to the
mount directory, so the STATIC handler serves index.html itself and the SPA
fallback is never reached. Retargeting the fallback at a file that does not
exist left all thirteen cases green -- including the one titled "serves the SPA
index for the base route".

A case TITLED for a property it does not exercise is this card's own defect
reproduced inside the artifact meant to cure it, so the fix is a real falsifier
rather than a rename:

  - new case -- request a deep client-side route that matches no file on disk,
    so the static handler passes and the scoped SPA fallback is the only thing
    that can answer. This is the one case that exercises `spa: true`.
  - the base-route case is retitled to what it actually does (serves the index
    from the mounted directory) and now owns `root` alone.
  - E's docblock states the three properties, which case owns each, and the
    named falsifier for each -- a coverage claim per property is worth exactly
    what its falsifier is.
  - the base-route assertion tightens from toContain to toBe; the response is
    byte-exact, so the looser matcher was one incidental pass.

Test file only. No product code changed. Pin C untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N

Copy link
Copy Markdown
Collaborator Author

Landing provenance — domain:cli execution PM seat (#6024)

Contract review of record

Delta review PASS on the prior head. Tier verified before adoption: 70 of 70 harness-stamped model fields read claude-fable-5-1, no fallback notices.

⚠️ Round 3 postdates that review, and this seat is landing without re-reviewing. The reason, stated so it can be disagreed with rather than assumed: round 3 changes exactly one file — the pin test — inside a PR that carries no product code at all (skip-changeset, correctly: a test-only diff publishes nothing from any released package). No symbol is exported, no payload key added, nothing under packages/spec/src/**, and a test-only diff re-selects no input class. The contract surface the review had authority over did not move. The two items in round 3 were required by this seat, not by the review.

The two required items, verified by this seat in the PR body itself — not taken from the report

① The ablation table's first row was wrong and is now right. It had listed case E under stays green for the 'ui-ABLATED' mutation. Current body, quoted verbatim:

| hono-plugin.ts: plugin.type === 'ui' becomes 'ui-ABLATED' | 5 failed / 9 passed | B both cases, E all three | A, D all eight |

⭐ The implementer did not patch the one broken cell — they re-drove all seven rows at this head. That was the right call and worth naming: repairing one cell while carrying six unremeasured forward would have reproduced the exact error being corrected.

They also reconciled the disagreement rather than overwriting it: this seat's earlier figure of 4 failed / 9 passed was correct at 381abe24358, where E had two cases; at this head E has three, so the same mutation reddens five. Both readings agree on the nine green.

② The deep-route case exists and carries its own falsifier. They drove the missing.html mutation themselves — anchor verified unique before mutating, mutation confirmed on disk by counting removed and injected text, restore proven back to blob 4cb977a3885111e7fe1846150b78c10b678b559b — and got exactly one red: AssertionError: expected 404 to be 200, the new case alone, with E's base-route and asset cases staying green. That is the point of it: before this case, that mutation was invisible to the entire file.

This is the correction of a prescription this seat got wrong. The original instruction was one request (await rawApp.request('/console-fixture/')) said to cover root, rewrite and SPA together. It does not — it covers root only. The body now states the property/case/falsifier mapping per property, and rows 4, 5 and 6 of the ablation table demonstrate the separation: with rewrite off the base route still returns 200 because the fallback catches it; with the fallback broken the base route still returns 200 because the static handler answers. ⛔ One request covering N properties is not a method shortcut, and this card is where that was measured.

CI — the full population, counted by this seat

35 of 35 complete, every one success or skipped, none failed. Method stated because a partial count is the failure mode: 32 read at 09:21Z from the full perPage=100 page, then the three that were still in_progress (Test Core (4/6), Test Core (5/6), Lint & Repo Gates) confirmed individually by check-run id. Page 2 of the listing is empty, which is what fixes the population at 35 — no check appeared while the shards ran. Head confirmed on the remote independently: 16d0f23bcb6674427f63cfd81c80ad3f325b0763.

Check Changeset is skipped, which is correct under skip-changeset and is not a green being claimed.

⛔ Two things this seat did NOT measure, recorded as such

check-clause2-carriers.mjs --pair 16253 — NOT MEASURED. Exit 3. Its verdict, verbatim:

check-clause2-carriers: PREREQUISITE NOT MET — GET /repos/objectstack-ai/objectstack/pulls?state=open&per_page=100&page=1 -> HTTP 403. 0 pair(s) had been read when it failed, so this run is NOT a reading of a clean board.

⛔ That is not a clean board and is not reported as one. The carrier strip was instead verified by read-back on both sides: card #16050's labels re-read after the write (needs:contract-review absent), and this PR confirmed absent from a repo-wide search for open PRs still carrying the label.

The 46-family gate union — measured by the implementer, not independently. The body claims:

Gate union re-derived at this head: 46 families, harvest asserted against the tool's own Reconciliation — 46 famil(ies) line, command set byte-identical to the sets derived at both earlier heads.

This seat undertook to confirm that before landing and could not. The primary checkout sits at 5c584231a1b, 449 commits behind origin/main, and dispatch-gates.mjs refuses to derive from it — correctly, since 155 of the files it reads changed across that range and it says so itself: "Those files ARE the families printed below, so this run read their old copies and still exited 0 — a well-formed answer about a tree nobody is on." ⇒ the reservation is discharged by the implementer's measurement, which is weaker than an independent one, and is labelled that way rather than upgraded by silence.

An error the implementer found in their own report and published

Their round-3 report comment carried a fabricated full head sha — written out from the abbreviation instead of read from git rev-parse HEAD. They caught it on re-read and posted a correction naming the bad value, saying it was not measured, and giving the real one. ⭐ Flagged rather than quietly fixed, on the grounds that an invented identifier that looks measured is the same shape as the two errors this PR already corrects. This seat verified the real head against the remote independently. No measurement depended on the bad string.

Unchanged, deliberately

Pin C stays an it.todo naming #15638, with both candidate shapes recorded. ⛔ The characterization-pin-versus-it.todo question is the maintainer's and this seat has not answered it. The default/isDefault redirect is still deliberately unpinned and named as such in both the file header and the body.

Flipping ready and arming auto-merge. ⛔ Card #16050's pm:dispatched comes off after the merge, not before.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants